package cn.percent.utils;

import cn.percent.sqlSession.SqlSession;

import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.*;

public class DBConnectionManager {
    private static DBConnectionManager m_dbInstance;
    private static int m_ClientNum;

    private Vector drivers = new Vector();
    private Hashtable pools = new Hashtable();
    public int nCount;
    public int iConID;

    public static synchronized DBConnectionManager getInstance(Properties config) throws Exception {
        if (m_dbInstance == null) {
            m_dbInstance = new DBConnectionManager(config);
        }
        m_ClientNum ++;
        return m_dbInstance;
    }

    private DBConnectionManager(Properties config) throws Exception {
        init(config);
    }

    public void freeConnection(String name, Connection con) {
        DBConnectionPool pool = (DBConnectionPool) pools.get(name);
        if (pool != null)
            pool.freeConnection(con);
    }

    public Connection getConnection(String name) {
        DBConnectionPool pool = (DBConnectionPool) pools.get(name);

        if (pool != null) {
            return pool.getConnection();
        }
        return null;
    }

    public Connection getConnection(String name, long time) {
        DBConnectionPool pool = (DBConnectionPool) pools.get(name);
        System.out.println(pool);
        if (pool != null) {
            return pool.getConnection(time);
        }
        return null;
    }

    public void init(Properties config) throws Exception {
        if (config == null) {
            System.out.println("load db properties failed...");
        }
        loadDriver(config);
        createPools(config);

    }

    public void release(){
        if (--m_ClientNum != 0){
            return;
        }
        Enumeration allPools = pools.elements();
        while(allPools.hasMoreElements()) {
           DBConnectionPool pool = (DBConnectionPool) allPools.nextElement();
           pool.release();
        }
        Enumeration allDrivers = drivers.elements();
        while(allDrivers.hasMoreElements()) {
           Driver driver = (Driver) allDrivers.nextElement();
           try{
               DriverManager.deregisterDriver(driver);
               System.out.println("Unregister JDBC Driver " + driver.getClass().getName());
           } catch (Exception e) {
               System.out.println("Can't unregister JDBC Driver " + driver.getClass().getName());
               e.printStackTrace();
           }
        }


    }

    private void loadDriver(Properties config) {
        String driverClass = config.getProperty("driverClass");
        StringTokenizer stringTokenizer = new StringTokenizer(driverClass);
        while (stringTokenizer.hasMoreElements()) {
            String driverClassName = stringTokenizer.nextToken().trim();
            try{
                Driver driver = (Driver) Class.forName(driverClassName).newInstance();
                DriverManager.registerDriver(driver);
                drivers.addElement(driver);
                System.out.println("Register JDBC " + driverClassName + " Success" );
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private void createPools(Properties properties) throws Exception {
        String environmentId = properties.getProperty("environmentId");
        String jdbcUrl = properties.getProperty("jdbcUrl");
        if (environmentId == null || jdbcUrl == null) {
            System.out.println("Not set jdbcUrl or environmentId for Connection Pool; ");
            throw new Exception("Not set jdbcUrl or environmentId for Connection Pool; ");
        }
        int maxConn = 0;
        String max = properties.getProperty("maxConn","50");
        try{
            maxConn = Integer.parseInt(max);
        } catch (Exception e) {
           maxConn = 50;
        }
        String username = properties.getProperty("username");
        String password = properties.getProperty("password");

        DBConnectionPool pool = new DBConnectionPool(environmentId, jdbcUrl, username, password, maxConn);
        pools.put(environmentId, pool);
        System.out.println("Create connection pool Success: " + environmentId);

    }

    class DBConnectionPool {
        private int checkOut;
        private Vector freeConnections = new Vector();
        private int maxConn;
        private String name;
        private String password;
        private String username;
        private String jdbcUrl;

        public DBConnectionPool(String name,String URL,String username,String password,int maxConn) {
            this.name = name;
            this.jdbcUrl = URL;
            this.username = username;
            this.password = password;
            this.maxConn = maxConn;
        }
        public synchronized void freeConnection(Connection con) {
            freeConnections.addElement(con);
            checkOut--;
            notifyAll();
        }
        public synchronized Connection getConnection() {
            Connection con = null;
            if (freeConnections.size() > 0) {
                con = (Connection) freeConnections.firstElement();
                freeConnections.removeElementAt(0);
                try{
                    if(con.isClosed() || !con.isValid(2)) {
                        System.out.println("Delete a connection from conn Pool: " + name);
                        checkOut--;
                        con = null;
                        con = getConnection();
                    }
                } catch (SQLException e) {
                    System.out.println("Delete a connection from conn Pool: " + name);
                    checkOut--;
                    con = null;
                    con = getConnection();
                }
            } else if (maxConn == 0 || checkOut < maxConn) {
                con = newConnection();
            } else {
                System.out.println("no more db connection");
            }
            if (con != null) {
                checkOut++;
            }
            return con;
        }

        public synchronized Connection getConnection(long timeout) {
            long startTime = 0;
            Connection con;
            while ((con = getConnection()) == null) {
                try{
                    wait(timeout);
                } catch (InterruptedException e) {

                }
                if (startTime ++ >= timeout) {
                    return null;
                }
            }
            return con;
        }

        public synchronized void release() {
            Enumeration allConnections = freeConnections.elements();
            while(allConnections.hasMoreElements()) {
                Connection con = (Connection) allConnections.nextElement();
                try {
                    con.close();
                    System.out.println("Close one connection of pool: " + name);
                } catch (SQLException e) {
                    e.printStackTrace();
                    System.out.println("Can't Close a connection of Pool: " + name);
                }
            }
            freeConnections.removeAllElements();
        }

        private Connection newConnection() {
            Connection con = null;
            try {
                if (username == null) {
                    con = DriverManager.getConnection(jdbcUrl);
                } else {
                    con = DriverManager.getConnection(jdbcUrl, username, password);
                }
                System.out.println("Create a new connection of pool: " + name);
            } catch (SQLException e) {
                e.printStackTrace();
                System.out.println("Can't Create connection of URL: " + jdbcUrl);
            }
            return con;
        }

    }


}
